package com.sky.service.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sky.constant.MessageConstant;
import com.sky.constant.RedisConstant;
import com.sky.constant.StatusConstant;
import com.sky.context.BaseContext;
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.dto.UserThredDTO;
import com.sky.entity.Category;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.exception.BaseException;
import com.sky.exception.UserNotLoginException;
import com.sky.mapper.DishMapper;
import com.sky.result.Result;
import com.sky.service.CategoryService;
import com.sky.service.IDishFlavorService;
import com.sky.service.IDishService;
import com.sky.utils.RedisCacheUtil;
import com.sky.utils.UserThreadUtil;
import com.sky.vo.DishPageVO;
import com.sky.vo.DishVO;
import com.sky.vo.LogicalField;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * <p>
 * 菜品 服务实现类
 * </p>
 *
 * @author xieminghui
 * @since 2023-11-16
 */
@Slf4j
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements IDishService {
    @Resource
    private IDishFlavorService dishFlavorService;
    @Resource
    private DishServiceImpl dishService;
    @Resource
    private CategoryService categoryService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisCacheUtil redisCacheUtil;
    // 注入已创建的线程池
    @Resource
    private ExecutorService FIXED_THREAD_POOL;
    /**
     * 分页带条件查询菜品
     *
     * @author: XieMingHui
     * @date: 23/11/17 21:42:58
     * @param: [dishPageQueryDTO]
     * @return: com.sky.result.Result<com.sky.vo.DishPageVO>
     **/
    @Override
    public Result<DishPageVO> findDishPage(DishPageQueryDTO dishPageQueryDTO) {
        // 先查菜品
        Page<Dish> dishPage = getDishPage(dishPageQueryDTO);
        List<Dish> records = dishPage.getRecords();
        List<Long> listCategory = new ArrayList<>(records.size());
        List<DishVO> list = new ArrayList<>(records.size());
        for (Dish dish : records) {
            // 得到菜品分类id
            listCategory.add(dish.getCategoryId());
            // 封装菜品属性到VO
            list.add(BeanUtil.toBean(dish, DishVO.class));
        }
        // 查询菜品分类名称,用分类id作为key封装到集合
        Map<Long, String> categoryMap = categoryService.listByIds(listCategory).stream()
                .collect(Collectors.toMap(Category::getId, Category::getName));
        // 遍历菜品集合
        for (DishVO dishVO : list) {
            Long categoryId = dishVO.getCategoryId();
            // 根据分类id查找集合中是否有对应的分类
            String name = categoryMap.get(categoryId);
            if (StrUtil.isNotBlank(name)) {
                // 有,封装到Vo
                dishVO.setCategoryName(name);
            }
        }
        return Result.success(new DishPageVO(dishPage.getTotal(), list));
    }

    /**
     * 查询菜品方法
     *
     * @author: XieMingHui
     * @date: 23/11/20 11:34:29
     * @param: [dishPageQueryDTO]
     * @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.sky.entity.Dish>
     **/
    private Page<Dish> getDishPage(DishPageQueryDTO dishPageQueryDTO) {
        Long page = dishPageQueryDTO.getPage();
        Long pageSize = dishPageQueryDTO.getPageSize();
        Long status = dishPageQueryDTO.getStatus();
        Long categoryId = dishPageQueryDTO.getCategoryId();
        String name = dishPageQueryDTO.getName();
        if (page == null) {
            page = 1L;
        }
        if (pageSize == null) {
            pageSize = 5L;
        }
        return lambdaQuery()
                .eq(status != null, Dish::getStatus, status)
                .eq(categoryId != null, Dish::getCategoryId, categoryId)
                .like(StrUtil.isNotBlank(name), Dish::getName, name)
                .page(new Page<>(page, pageSize));
    }

    /**
     * 新增菜品
     *
     * @author: XieMingHui
     * @date: 23/11/20 10:11:20
     * @param: [dishDTO]
     * @return: com.sky.result.Result<java.lang.String>
     **/
    @Override
    public Result<String> saveDish(DishDTO dishDTO) {
        UserThredDTO userThredDTO = UserThreadUtil.getThread();
        // 判断菜品是否重复
        String name = dishDTO.getName();
        Integer count = lambdaQuery().eq(StrUtil.isNotBlank(name), Dish::getName, name).count();
        if (count > 0) {
            throw new BaseException("菜品已存在");
        }
        log.info("前端请求参数:{}", dishDTO);
        Dish dish = BeanUtil.toBean(dishDTO, Dish.class);
        // 添加时间和人的字段
        LocalDateTime now = LocalDateTime.now();
        Long currentId = userThredDTO.getId();
        dish.setCreateTime(now);
        dish.setCreateUser(currentId);
        dish.setUpdateTime(now);
        dish.setUpdateUser(currentId);
        List<DishFlavor> flavorList = dishDTO.getFlavors();
        // 事务不生效,注入自己的对象调用内部方法让事务生效
        dishService.addDish(dish, flavorList);
        return Result.success("新增成功");
    }

    // 新增菜品
    @Transactional(rollbackFor = Exception.class)
    public void addDish(Dish dish, List<DishFlavor> flavorList) {
        save(dish);
        Long dishId = dish.getId();
        for (DishFlavor dishFlavor : flavorList) {
            dishFlavor.setDishId(dishId);
        }
        dishFlavorService.saveBatch(flavorList);
    }

    /**
     * 修改菜品
     *
     * @author: XieMingHui
     * @date: 23/11/20 10:11:50
     * @param: [dishDTO]
     * @return: com.sky.result.Result<java.lang.String>
     **/
    @Override
    @Transactional
    public Result<String> updateDish(DishDTO dishDTO) {
        UserThredDTO userThredDTO = UserThreadUtil.getThread();
        Dish dish = BeanUtil.toBean(dishDTO, Dish.class);
        // 添加时间和人的字段
        LocalDateTime now = LocalDateTime.now();
        Long currentId = userThredDTO.getId();
        dish.setUpdateTime(now);
        dish.setUpdateUser(currentId);
        updateById(dish);
        // 口味
        QueryWrapper<DishFlavor> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(DishFlavor::getDishId, dishDTO.getId());
        int count = dishFlavorService.count(wrapper);
        // 删除口味
        if (count > 0) {
            //如果原有口味有数据,则删除
            log.info("如果原有口味有数据,则删除");
            dishFlavorService.remove(wrapper);
        }
        // 重新添加口味
        dishFlavorService.saveBatch(dishDTO.getFlavors());
        return Result.success("修改成功");
    }

    /**
     * 修改时查询回显
     *
     * @author: XieMingHui
     * @date: 23/11/20 10:14:56
     * @param: [菜品id]
     * @return: 菜品信息
     **/
    @Override
    public Result<DishVO> findById(Long id) {
        if (id == null) {
            throw new RuntimeException("菜品id为空");
        }
        Dish dish = getById(id);
        DishVO dishVO = BeanUtil.toBean(dish, DishVO.class);
        List<DishFlavor> flavorList = dishFlavorService.lambdaQuery()
                .eq(DishFlavor::getDishId, id)
                .list();
        dishVO.setFlavors(flavorList);
        return Result.success(dishVO);
    }

    /**
     * 启用和禁用菜品
     *
     * @author: XieMingHui
     * @date: 23/11/20 12:15:37
     * @param: [status, id]
     * @return: 返回操作结果
     **/
    @Override
    public Result<String> enableDisable(String status, Long id) {
        UserThredDTO userThredDTO = UserThreadUtil.getThread();
        LocalDateTime now = LocalDateTime.now();
        Long currentId = userThredDTO.getId();
        boolean update = lambdaUpdate()
                .eq(id != null, Dish::getId, id)
                .set(StrUtil.isNotBlank(status), Dish::getStatus, status)
                .set(Dish::getUpdateTime, now)
                .set(Dish::getUpdateUser, currentId)
                .update();
        if (update) {
            return Result.success("操作成功");
        } else {
            return Result.success("操作失败");
        }
    }

    /**
     * 删除菜品
     *
     * @author: XieMingHui
     * @date: 23/11/20 12:24:53
     * @param: [ids]
     * @return: 操作结果
     **/
    @Override
    @Transactional
    public Result<String> deleteDish(List<Long> ids) {
        removeByIds(ids);
        log.info("菜品删除成功,被删除的id:{}", ids);
        QueryWrapper<DishFlavor> wrapper = new QueryWrapper<>();
        wrapper.lambda().in(DishFlavor::getDishId, ids);
        dishFlavorService.remove(wrapper);
        log.info("菜品口味删除成功,被删除的id:{}", ids);
        return Result.success("操作成功");
    }

    /**
     * 根据分类id查询对应菜品
     *
     * @author: XieMingHui
     * @date: 23/11/20 18:01:24
     * @param: [categoryId]
     * @return: com.sky.result.Result<java.util.List < com.sky.vo.DishVO>>
     **/
    @Override
    public Result<List<DishVO>> findDishByCategory(Integer categoryId) {
        List<Dish> list = lambdaQuery()
                .eq(categoryId != null, Dish::getCategoryId, categoryId)
                .list();
        List<DishVO> listD = new ArrayList<>(list.size());
        for (Dish dish : list) {
            listD.add(BeanUtil.toBean(dish, DishVO.class));
        }
        return Result.success(listD);
    }

    /**
     * 条件查询菜品和口味
     *
     * @param categoryId
     * @return
     */
    @Override
    public Result<List<DishVO>> listWithFlavor(Long categoryId) {
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        //redis中数据的key
        String key = RedisConstant.CATEGORY_KEY + categoryId;
        //查询redis
        String json = opsForValue.get(key);
        //穿透防护,如果查到的是""值
        if ("".equals(json)) {
            System.out.println("查到了长度为0的字符串的数据,就是缓冲穿透数据直接返回");
            return Result.success(Collections.emptyList());
        }
        if (StrUtil.isBlank(json)) {
            //如果数据不存在就尝试获取锁更新Redis
            //定义锁的id
            String lockKey = "lock:key" + key;
            boolean tryLock = redisCacheUtil.tryLock(lockKey);
            if (!tryLock) {
                //如果没有获取到锁,表示有人正在更新Redis了
                System.out.println("时间过期了,没有获取到锁,表示有人正在更新Redis了,返回过期的信息");
                return Result.success(Collections.emptyList());
            }
            // 定义方法开启新线程执行查询数据库,更新redis的任务
            getDatabase(categoryId, key, lockKey);
            //如果没有数据直接返回
            System.out.println("没有数据开启线程执行更新,本次直接返回");
            return Result.success(Collections.emptyList());
        }
        //如果查到了数据,转换成对象
        LogicalField logicalField = JSONUtil.toBean(json, LogicalField.class);
        String data = JSONUtil.toJsonStr(logicalField.getData());
        System.out.println("查到了数据:" + data);
        List<DishVO> dishVOList = JSONUtil.toList(data, DishVO.class);
        log.info("查询到的数据:{}", dishVOList);
        //获取逻辑过期时间字段
        LocalDateTime LogicTime = logicalField.getLogicTime();
        //比较现在的时间判断是否过期
        if (LogicTime.isAfter(LocalDateTime.now())) {
            //如果逻辑过期时间在现在时间之后,则没有过期,直接返回信息
            System.out.println("逻辑过期时间在现在时间之后,没有过期,返回商铺信息");
            return Result.success(dishVOList);
        }
        //如果过期了就尝试获取锁更新Redis
        //定义锁的id
        String lockKey = "lock:key" + key;
        boolean tryLock = redisCacheUtil.tryLock(lockKey);
        if (!tryLock) {
            //如果没有获取到锁,表示有人正在更新Redis了
            System.out.println("时间过期了,没有获取到锁,表示有人正在更新Redis了,返回过期的信息");
            return Result.success(dishVOList);
        }
        // 定义方法开启新线程执行查询数据库,更新redis的任务
        getDatabase(categoryId, key, lockKey);
        //本身直接返回过期的信息;
        System.out.println("获取到锁了,开启新线程去执行更新redis数据的逻辑,本线程直接返回过期的信息");
        return Result.success(dishVOList);
    }

    /**
     * 定义方法开启新线程执行查询数据库,更新redis的任务
     *
     * @author: XieMingHui
     * @param: [type, key, lockKey]
     * @return: void
     **/
    private void getDatabase(Long categoryId, String key, String lockKey) {
       // ExecutorService FIXED_THREAD_POOL = Executors.newFixedThreadPool(1);
        //如果获取到锁了,开启新的线程去查询数据库完成更新redis操作
        FIXED_THREAD_POOL.submit(() -> {
            try {
                //数据库查询对应菜品类型的菜品的方法
                List<DishVO> listVO = getDishVOS(categoryId);
                if (listVO.size() == 0) {
                    //数据库没有该数据,防止缓存穿透,存入空串
                    stringRedisTemplate.opsForValue().set(key, "", 2, TimeUnit.MINUTES);
                }
                //更新redis中逻辑过期字段
                redisCacheUtil.setRedis(key, listVO, RedisConstant.LOGIC_TIME, TimeUnit.DAYS);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                //释放锁
                System.out.println("查询数据库,成功更新redis,删除存入的锁,释放锁========================");
                //删除存入的数据
                stringRedisTemplate.delete(lockKey);
            }
        });
    }

    /**
     * 数据库查询对应菜品类型所有菜品和菜品对应口味的方法
     *
     * @author: XieMingHui
     * @param: [categoryId]
     * @return: java.util.List<com.sky.vo.DishVO>
     **/
    private List<DishVO> getDishVOS(Long categoryId) {
        // 根据分类id查询起售中的菜品
        List<Dish> dishList = lambdaQuery()
                .eq(categoryId != null, Dish::getCategoryId, categoryId)
                .eq(Dish::getStatus, StatusConstant.ENABLE)
                .list();
        // 封装VO
        List<DishVO> listVO = new ArrayList<>(dishList.size());
        // 封装菜品id
        List<Long> listDishId = new ArrayList<>(dishList.size());
        for (Dish dish : dishList) {
            listVO.add(BeanUtil.toBean(dish, DishVO.class));
            listDishId.add(dish.getId());
        }
        //用id查询菜品口味
        List<DishFlavor> dishFlavorList = dishFlavorService.lambdaQuery()
                .in(DishFlavor::getDishId, listDishId)
                .list();
        // 封装到菜品中
        EncapsulationFlavor(listVO, dishFlavorList);
        return listVO;
    }

    /**
     * 封装菜品口味到菜品中
     *
     * @author: XieMingHui
     * @param: [listVO, dishFlavorList]
     * @return: void
     **/
    private void EncapsulationFlavor(List<DishVO> listVO, List<DishFlavor> dishFlavorList) {
        Map<Long, DishVO> dishVOMap = listVO.stream().collect(Collectors.toMap(DishVO::getId, v -> v));
        for (DishFlavor dishFlavor : dishFlavorList) {
            DishVO dishVO = dishVOMap.get(dishFlavor.getDishId());
            if (dishVO != null) {
                dishVO.getFlavors().add(dishFlavor);
            }
        }
    }

}
